/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.igorov.efficientsoft.business.similaridad;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;

/**
 *
 * @author igorov
 */
public class SimilaridadStringsCoseno implements SimilaridadStrings{

    public SimilaridadStringsCoseno() {
    }
    
    /**
     * Obtiene un porcentaje de similaridad entre dos cadenas de texto
     * @param texto1
     * @param texto2
     * @return similaridad entre los textos de entrada
     */
    public Float getSimilaridad(String texto1, String texto2) {
        String[] palabras1 = separarPalabras(texto1.toLowerCase());
        String[] palabras2 = separarPalabras(texto2.toLowerCase());
        
        Map<String, Float> frecuenciaTabla1 = preparaFrecuencia(palabras1);
        Map<String, Float> frecuenciaTabla2 = preparaFrecuencia(palabras2);
        
        Map<String, Float> tablaFactorizada1 = factorizarTabla(frecuenciaTabla1);
        Map<String, Float> tablaFactorizada2 = factorizarTabla(frecuenciaTabla2);
        
        Map<String, Float>[] tablas = new HashMap[2];
        tablas[0] = tablaFactorizada1;
        tablas[1] = tablaFactorizada2;
        
        completaTablas(tablas);
        
        Float similaridad = null;
        try {
            similaridad = cosenoSimilaridad(tablaFactorizada1, tablaFactorizada2);
        }catch(Exception e){
        }
        
        return similaridad;
    }
    
    /**
     * Separa un parrafo en un arreglo de las palabras que las contiene
     * @param cadena parrafo de entrada
     * @return arreglo de palabras correspondientes al parrafo de entrada
     */
    private String[] separarPalabras(String cadena){
        StringTokenizer tokens = new StringTokenizer(cadena);
        String[] palabras = new String[tokens.countTokens()];
        int i = 0;
        while (tokens.hasMoreTokens()) {
            palabras[i++] = tokens.nextToken();
        }
        
        return palabras;
    }
    
    /**
     * Crea un mapa en la cual la clave es cada palabra del arreglo de entrada,
     * y su valor es la cantidad de veces que esta presente esa palabra en el
     * arreglo
     * @param palabras arreglo palabras para encontrar la frecuencia de cada palabra
     * @return un mapa con cada palabra con su respectiva frecuencia
     */
    Map<String, Float> preparaFrecuencia(String[] palabras){
        Map<String, Float> tabla = new HashMap<String, Float>();
        
        for (String string : palabras) {
            if (tabla.containsKey(string)) {
                tabla.put(string, tabla.get(string) + 1F);
            }
            else
                tabla.put(string, 1F);
        }
        return tabla;
    }
    
    /**
     * Obtiene una mapa en los que cada valor se divide entre la suma total de valores
     * del mapa
     * @param tabla mapa que contiene como llave una palabra y como valor un decimal
     * @return un mapa con cada valor dividido entre la suma de valores del mapa
     */
    Map<String, Float> factorizarTabla(Map<String, Float> tabla){
        Float suma = 0F;
        Iterator it = tabla.entrySet().iterator();
        
        while (it.hasNext()) {
		Map.Entry e = (Map.Entry)it.next();
                suma += (Float)e.getValue();
	}
        
        Map<String, Float> tablaFactorizada = new HashMap<String, Float>();
        it = tabla.entrySet().iterator();
        while (it.hasNext()) {
		Map.Entry e = (Map.Entry)it.next();
                tablaFactorizada.put(e.getKey().toString(), (Float)e.getValue() / suma);
	}
        
        return tablaFactorizada;
    }
    
    private void completaTablas(Map<String, Float>[] tablas){
        for (int i = 0; i < tablas.length; i++) {
                for (int j = 1; j < tablas.length; j++) {
                    completaDosTablas(tablas[i], tablas[j]);
                }
        }
    }
    
    /**
     * Completa las tablas respecto al otro mapa
     * Si el valor de algun mapa no esta incluido en el otro mapa, entonces
     * se le agrega
     * @param tabla1
     * @param tabla2 
     */
    private void completaDosTablas(Map<String, Float> tabla1, Map<String, Float> tabla2){
        Iterator it;
        
        //Completando la tabla 1
        it = tabla1.entrySet().iterator();
        while (it.hasNext()) {
		Map.Entry e = (Map.Entry)it.next();
                if (!tabla2.containsKey(e.getKey())) {
                    tabla2.put(e.getKey().toString(), 0F);
                }
	}
        
        //Completando la tabla 2
        it = tabla2.entrySet().iterator();
        while (it.hasNext()) {
		Map.Entry e = (Map.Entry)it.next();
                if (!tabla1.containsKey(e.getKey())) {
                    tabla1.put(e.getKey().toString(), 0F);
                }
	}
    }
    
    private Float cosenoSimilaridad(Map<String, Float> tabla1, Map<String, Float> tabla2) throws Exception{
        if (tabla1.size() != tabla2.size()){
                throw new Exception("Las tablas deberian tener el mismo tamanho");
        }
        
        //length de las tablas
        Float length1 = 0F;
        Float length2 = 0F;
        
        Float secValue;
        //Suma del vector multiplicacion
        Float svMult = 0F;
        
        Iterator it = tabla1.entrySet().iterator();
        while (it.hasNext()) {
		Map.Entry e = (Map.Entry)it.next();
                double t = Math.pow((Float)e.getValue(), 2);
                length1 += Float.valueOf(String.valueOf(t));

                secValue = tabla2.get(e.getKey().toString());
                String t2 = String.valueOf(Math.pow(secValue, 2));
                length2 += Float.valueOf(t2);

                svMult += secValue * (Float)e.getValue();
	}
        String t3 = String.valueOf(Math.sqrt(length1));
        length1 = Float.valueOf(t3);

        String t4 = String.valueOf(Math.sqrt(length2));
        length2 = Float.valueOf(t4);

        Float sim = svMult / (length1 * length2);
        return sim;
    }
    
}
